<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手写Call()&手写apply</title>
</head>

<body>
    <script>
        // 变更函数调用者
        function foo() {
            console.log(this.name);
            console.log(arguments)
        }
        // 测试
        const obj = {
            name: "zs"
        }
        obj.foo = foo;
        // 变更foo的调用者，即改变this指向
        obj.foo()

        // 基于以上原理，我们两句代码就可以实现call()
        // Function.prototype.myCall = function(thisArg,...args){
        //     thisArg.fn = this;
        //     // this指向调用call的对象，即我们要改变this指向的函数
        //     return thisArg.fn(...args);
        // }
        Function.prototype.myCall = function (thisArg, ...args) {
            if (typeof this !== "function") {
                throw new TypeError('error')
            }
            const fn = Symbol('fn'); //防止fn覆盖已有属性
            thisArg = thisArg || window;
            thisArg[fn] = this;
            const result = thisArg[fn](...args) //执行当前函数
            delete thisArg[fn]
            return result;
        }
        foo.myCall(obj);

        /*
        apply()与call()类似，区别在于call()接收参数列表，而apply()接收一个参数数组
        */
        Function.prototype.myApply = function (thisArg, args) {
            if (typeof this !== "function") {
                throw new TypeError('error')
            }
            const fn = Symbol('fn'); //防止fn覆盖已有属性
            thisArg = thisArg || window;
            thisArg[fn] = this;
            const result = thisArg[fn](args) //执行当前函数
            delete thisArg[fn]
            return result;
        }
        foo.myApply(obj, []);

        // 手写bind()
        Function.prototype.myBind = function (thisArg, ...args) {
            return () => {
                this.apply(thisArg, args)
            }
        }
        /*
        我们忽略了三点：
        1、bind()除了this，还接收其他参数，bind()返回的函数也接收参数，这两部分的参数都要传给返回的函数
        2、new的优先级：如果bind绑定后的函数被new了，那么此时this指向就发生改变。此时的this就是当前函数的实例
        3、没有保留原函数在原型链上的属性和方法
        */
        Function.prototype.myBind = function (thisArg, ...args) {
            if (typeof this !== "function") {
                throw new TypeError("Bind must be called on a function")
            }
            var self = this;
            var fbound = function () {
                self.apply(this instanceof self ? this : thisArg, args.concat(Array.prototype.slice.call(
                    arguments)))
            }
            fbound.prototype = Object.create(self.prototype);
            return fbound;
        }
    </script>
</body>

</html>
